package playGround.square;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import playGround.border.Border;
import playGround.border.Door;
import playGround.dungeon.Direction;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;


/**
 * @invar 
 * @author 	Maarten Christiaen, Niels Claeys
 * @version 2.2
 */
public abstract class TeleportationSquare extends Square{
	
	/**
	 * The constructor of the Square class.
	 * @param   {@inheritDoc}
	 * @param   borders
	 * 			the borders of the square
	 * @pre     {@inheritDoc}
	 * @post	{@inheritDoc}
	 * @post    The borders of this square are set to borders when the borders are valid
	 * 			| if(canHaveBorders(borders))
	 * 			| this.addBorder(borders)
	 * @post	The directions are initialized
	 * 			| this.initializeDirections()
	 */
	@Raw
	public TeleportationSquare(int temperature, BigDecimal humidity,
			Scale temperatureScale,Map<Direction, Border> borders) throws IllegalArgumentException {
		super(temperature, humidity, temperatureScale);
		this.addBorder(borders);
		this.teleportationDestinations=new HashSet<Square>();
	}
	/**
	 * This square is initialised with the parameters temperature, humidity and temperatureScale
	 * @param temperature the temperature of the square
	 * @param humidity the humidity of the square
	 * @param temperatureScale the temperatureScale of the square
	 * @post 
	 * 		|super(temperature,humidity,temperatureScale)
	 */
	@Raw
	public TeleportationSquare(int temperature,BigDecimal humidity,Scale temperatureScale){
		super(temperature,humidity,temperatureScale);
		this.teleportationDestinations=new HashSet<Square>();

	}
	/**
	 * this method says whether or not this border can exist in that direction
	 * @param {@inheritDoc}
	 * @return 
	 * 		|if(getNbDoors(super.getAllBorders()>2&&border instanceof Door)
	 * 		|then result == false
	 * 		|if(!super.canHaveAsBorder(direction, border))
	 * 		|then result==false	
	 */
	@Override
	public boolean canHaveAsBorder(Direction direction, Border border){
		if(border instanceof Door){
			if(getNbDoors(super.getAllBorders())>2)
				return false;
		}
		return super.canHaveAsBorder(direction, border);
		
	}
	/**
	 * This mthod checks the number of doors of the map
	 * @param borders the map with direction and its border
	 * @return the number of doors in borders
	 * 		| for each door in borders.values()
	 * 		| amountDoors++
	 * 		| result == amountDoors
	 */
	public int getNbDoors(Map<Direction,Border> borders) {
		int amountDoors = 0;
		for(Border borderOfMap: borders.values() ){
			if(borderOfMap instanceof Door)
				amountDoors++;
		}
		return amountDoors;
	}


	/**
	 * the method checks whether or not the map of borders is valid
	 * @param {@inheritDoc}
	 * @return {@inheritDoc}
	 * 		| for each door in super.getAllBorders().values() and for each door in borders
	 * 		|	count++
	 * 		| if(count >3)
	 * 		| then result == false 
	 */
	@Override
	public boolean canHaveBorders(Map<Direction, Border> borders) {
		boolean check=super.canHaveBorders(borders);
		return check && (getNbDoors(super.getAllBorders())+getNbDoors(borders)) <=3;
	}
	/**
	 * The method checks whether the map borders is valid.
	 * @return  {@inheritDoc}
	 * @return 	true if there aren't more than 3 doors bordering this square.
	 * 			|for each border in getAllBorders().values()
	 * 			|	if(border instanceof Door)
	 * 			|		amountDoors++
	 * 			|if(amountDoors>3)
	 * 			| then result == false
	 */
	@Override
	public boolean areValidBorders(){
		boolean check=super.areValidBorders();
		return check&&getNbDoors(super.getAllBorders())<=3;
	}
	/**
	 * This method overrides the method of setTemperature in square.
	 * @post 
	 * 		|super.setTemperature(temperature)
	 * @post	
	 * 		| for each rock in get getNeighbouringSquaresWhoseTemperatureIsDependentOnNeighbours
	 * 		| rock.calculateTemperature()
	 */
	@Override
	public void setTemperature(int temperature){
		super.setTemperature(temperature);
		for(Square rock:getNeighbouringSquaresWhoseTemperatureIsDependentOnNeighbours())
			((Rock)rock).changeTemperature();
	}
	/**
	 * This method searches the rocks of a surrounding square
	 * @return the number of rocks surrounding this square
	 * 		|List<Square> neighbouringRocks = new ArrayList<Square>()
	 * 		| for each border in this.getAllBorders().values()
	 * 		| if(border != null && border.getNbSquares() == 2)
	 * 		| if(border.getSquareAt(0) != this && border.getSquareAt(0).temperatureIsDependentUponNeighbours())
	 * 		| then neighbouringRocks.add(square)
	 * 		| result == neighbouringRocks
	 */
	public List<Square> getNeighbouringSquaresWhoseTemperatureIsDependentOnNeighbours(){
		List<Square> neighbouringRocks = new ArrayList<Square>();
		for(Border border: this.getAllBorders().values()){
			if(border != null){
				if(border.getNbSquares() ==2){
					Square square = border.getSquareAt(0);
					if(square ==this)
						square = border.getSquareAt(1);
					if(square.temperatureIsDependentUponNeighbours())
						neighbouringRocks.add(square);
				}
			}
		}
		return neighbouringRocks;
				
	}
	/**
	 * This method checks whether or not this square can navigate to the given square.
	 * @param square: the square you want to navigate to
	 * @return true if this square can teleport to the given square or if they are in the same area
	 * 		| if(square.isInSameArea(this))
	 * 		| then result == true
	 * 		| if(this.canTeleportTo(square))
	 * 		| then result == true
	 * @return 	true if you can navigate to the given square through one of the teleportdestinations.
	 * 			|for each Square teleportDestination in this.getTeleportationDestinations()
	 * 			|if(teleportDestination.canNavigateTo(square)
	 * 			|	 result == true
	 * @pre the given square cannot be null.
	 * 		|square!=null
	 */
	@Override
	public boolean canNavigateTo(Square square){
		return navigation(square, new HashSet<Square>());
	}
	/**
	 * This method checks whether or not this square can navigate to the given square.
	 * @param square: the square you want to navigate to
	 * @param alllreadyDone: a set containing the squares allready checked.
	 * @return true if this square can teleport to the given square or if they are in the same area
	 * 		| if(square.isInSameArea(this))
	 * 		| then result == true
	 * 		| if(this.canTeleportTo(square))
	 * 		| then result == true
	 * @return 	true if you can navigate to the given square through one of the teleportdestinations.
	 * 			|for each Square teleportDestination in this.getTeleportationDestinations()
	 * 			|	if(!allreadyDone.contains(teleportDestination))
	 * 			|		if (teleportDestination.navigation(square,allreadyDone))
	 * 			|			result == true
	 * @pre the given square cannot be null and neither can the given set.
	 * 		|square!=null
	 * 		|allreadyDone!=null
	 */
	private boolean navigation(Square square, Set<Square> allreadyDone){
		if(square.isInSameArea(this))
			return true;
		if(this.canTeleportTo(square))
			return true;
		allreadyDone.add(this);
		for(Square teleportDestination: this.getTeleportationDestinations()){
				if(!allreadyDone.contains(teleportDestination))
					if(((TeleportationSquare) teleportDestination).navigation(square,allreadyDone))
						return true;
		}
		return false;
	}
	/**
	 * Method to check if you can teleport to the given square.
	 * @param square square you want to check whether you can teleport to it.
	 * @return 	True if containsTeleportDestination(square) is true.
	 * 			|result==this.containsTeleporDestination(square)
	 */
	public boolean canTeleportTo(Square square) {
		return this.containsTeleportDestination(square);
	}
	/**
	 * Gets the set containing the teleportation destinations.
	 * @return the teleportationDestination
	 */
	@Basic
	public Set<Square> getTeleportationDestinations(){
		return new HashSet<Square>(this.teleportationDestinations);
	}
	/**
	 * Variable containing the Squares this square can teleport to.
	 */
	private Set<Square> teleportationDestinations;
	/**
	 * Method to add a Square as a destination for teleportation of this square.
	 * @param square square to add as teleportationdestination.
	 * @pre The given square must be a valid teleportation destination for this square.
	 * 		|canHaveTeleportationDestination(square)
	 * @post The given square is added to the set.
	 * 		|new.getTeleportationDestinations().contains(square)
	 * @post The size of the teleportationDestination set is incremented by 1.
	 * 		|new.getTeleportationDestinations().size()==this.getTeleportationDestinations().size()+1
	 */
	public void addTeleportationDestination(Square square){
		assert(canHaveTeleportationDestination(square));
		this.teleportationDestinations.add(square);
	}
	/**
	 * Method to remove the given square from the teleportation destinations of this square.
	 * @param 	square the square to remove
	 * @post	the square is removed from the destination.
	 * 			|!this.getTeleportationDestinations().contains(square)	
	 * @post	the set of teleportationsdestinations is decremented by 1.
	 * 		|new.getTeleportationDestinations().size()==this.getTeleportationDestinations().size()-1
	 */
	public void removeTeleportationDestination(Square square){
		this.teleportationDestinations.remove(square);
	}
	/**
	 * Checks whether the given square is a valid teleportationDestination.
	 * @param square the square you want to check
	 * @return False if the square is null, or is a Rock.
	 * 			|if(square==null||square instanceof Rock)
	 * 			|then result==false
	 */
	public boolean canHaveTeleportationDestination(Square square){
		if(square==null)
			return false;
		if(square instanceof Rock)
			return false;
		return true;
	}
	/**
	 * Checks if the teleportation destinations of this square contains the given square.
	 * @param square the square you want to check.
	 * @return whether the square is in the set of destinations
	 * 			|result==this.getTeleportationDestinations().contains(square)
	 */
	public boolean containsTeleportDestination(Square square){
		return this.teleportationDestinations.contains(square);
	}
	
	/**
	 * This method says whether or not this square can teleport
	 */
	@Basic
	public boolean canTeleport(){
		return canTeleport;
	}
	/**
	 * This variable says whether the square can teleport
	 */
	private boolean canTeleport;
	/**
	 * This method changes canTeleport() to the boolean teleport
	 * @param teleport whether or not this square can teleport
	 * @post canTeleport() will now return the given teleport
	 * 		| new.canTeleport() == teleport
	 */
	public void changeCanTeleport(boolean teleport){
		canTeleport = teleport;		
	}

	/**
	 * This method says whether or not the temperature of the square is dependent on the neighbouringSquares
	 * @return false
	 */
	@Override @Immutable
	public boolean temperatureIsDependentUponNeighbours(){
		return false;
	}
	


}
